using IQIGame.Onigao.Game;
using IQIGame.Onigao.GamePlay;

namespace IQIGame.Onigao.Logic
{
    /// <summary>
    /// 四方向推动
    /// 参数组1:1：实体tag（0代表自己），2：最大距离（途中遇到其他实体会停下）
    /// </summary>
    public class ExecPushEntityFourWay : BaseServiceExecution
    {
        public override void Execute(LevelRegion region, LevelPlayer player)
        {
            var config = _config as ExecParamPushEntityFourWay;
            if (!config.tagOrMcid.TryGetValue(triggerEntity, out int tagOrMcid))
            {
                LogicLog.LogError("无法获取到tagOrMcid.");
                return;
            }
            int maxDistance = config.maxDistance;
            LevelEntity entity = tagOrMcid == 0 ? triggerEntity : region.GetEntityByTagOrMcid(tagOrMcid);
            if (entity == null)
            {
                LogicLog.LogError($"根据tagOrMcid: {tagOrMcid} 找不到实体。");
                return;
            }
            ObstacleInfo obstacle = entity.entityConfig.obstacle;
            if (obstacle == null || obstacle.invalid || !obstacle.isCapsule)
            {
                LogicLog.LogError($"仅胶囊阻挡实体可推动。Error service: {serviceCid}, entity: [tagOrMcid={tagOrMcid}, mcid={entity.entityConfig?.id}]");
                return;
            }
            if (entity.isOccupied)
            {
                LogicLog.LogError("目标实体正被占用");
                return;
            }
            if (triggerEntity.isOccupied)
            {
                LogicLog.LogError("当前实体正被占用");
                return;
            }

            //注意以下代码都使用二维矢量计算
            Vector2Logic entityPos2D = new Vector2Logic(entity.position.x, entity.position.z);
            //根据英雄和实体的位置，计算离英雄->实体方向上最近的可推动方向
            Vector3Logic v3vHeroToEntity = entity.position - player.entity.position;
            Vector2Logic v2HeroToEntity = new Vector2Logic(v3vHeroToEntity.x, v3vHeroToEntity.z);
            //计算推动方向
            Vector2Logic dirPosX = new Vector2Logic(1, 0);
            Vector2Logic moveDir = GetPushDir(dirPosX, entity.dir, v2HeroToEntity);
            //获取移动范围内所有实体，并计算它们和当前实体（箱子）的碰撞点
            //理想最终位置（没有阻挡）farthest end point
            Vector2Logic endPoint = entityPos2D + moveDir * maxDistance;

            entity.OccupiedBy(EntityOccupationStatus.BeingPush, endPoint, player.entity);

            region.NotifyPushEntity(player, entity, maxDistance);

            ////实际最终位置
            //Vector2Logic finalEndPoint = endPoint;
            //#region 从移动范围内的实体中找到最近的碰撞点
            //List<LevelEntity> entitiesAround = ListPool<LevelEntity>.Get();
            //{
            //    entity.region.terrain.QuadSearchEntity(entitiesAround, player.entity.position);
            //    //如果有其他实体，则计算碰撞点，如果没有按照maxDistance方向移动的最终点就是目标点
            //    if (entitiesAround.Count > 0)
            //    {
            //        TransableVertexOBBRect myRect = null;
            //        float minSqrDistance = float.MaxValue;
            //        for (int i = 0; i < entitiesAround.Count; i++)
            //        {
            //            LevelEntity otherEntity = entitiesAround[i];
            //            ObstacleInfo otherObstacle = otherEntity.entityConfig.obstacle;
            //            if (otherEntity != triggerEntity && otherObstacle != null && !otherObstacle.invalid)
            //            {
            //                //当前实体（箱子）是一个可移动的阻挡物，也就是Recast agent，只能是胶囊（圆形）
            //                //需要考虑阻挡物的形状，不能都当成圆形处理，因为阻挡物可能很大，把矩形当成圆形处理会有很大误差
            //                if (otherObstacle.isCapsule) //如果碰撞障碍是圆形
            //                {
            //                    //预测圆形碰撞，获得停止点
            //                    if (GeometryUtil.PredictFirstCircleIntersect(entityPos2D, obstacle.radius,
            //                        endPoint, otherEntity.position, otherObstacle.radius,
            //                        out var endCenterPoint))
            //                    {
            //                        float sqrDistance = (endCenterPoint - entityPos2D).sqrMagnitude;
            //                        if (sqrDistance < minSqrDistance)
            //                        {
            //                            finalEndPoint = endCenterPoint;
            //                            minSqrDistance = sqrDistance;
            //                        }
            //                    }
            //                }
            //                else //如果碰撞障碍是矩形
            //                {
            //                    //这里先把箱子的阻挡圆转换成最小外包围矩形（顶边与移动方向垂直），然后再和矩形障碍物做碰撞计算。
            //                    //结果可能会有误差，但由于箱子通常都不大，所以一般来说误差不明显，且箱子通常是一个立方体形象，本身的形状和阻挡圆就会有一定的穿插和误差。
            //                    //所以视觉上不会有太大影响。

            //                    //其实直接计算圆和矩形的碰撞也可以，但具体思路是功能完成后想到的，暂时没有重新开发的时间。
            //                    //而且我也考虑过，首先两种算法都要考虑两种情况，一种是碰撞点为矩形顶点，一种碰撞点在矩形某条边上
            //                    //其次依然要遍历四条边分别计算，且圆形和矩形的切点计算比直接计算矩形碰撞的直线相交更复杂，这里会多一些消耗。
            //                    //但矩形碰撞为了方便前后做了两次坐标轴转换，所以也会有一些多余的性能消耗。（如果不转换容易出错且计算是否是顶点和圆碰撞，以及通过交点计算center时还需要更多的运算）
            //                    //初步对比，“两个矩形碰撞”，“圆形和矩形碰撞”两者性能上差别不大，但矩形碰撞的思路更简单清晰，不容易出错，更好debug。
            //                    //如果后期对精度有更高的要求，再换成圆形和矩形碰撞的算法。

            //                    //创建箱子的碰撞矩形，只创建一次。
            //                    if (myRect == null)
            //                    {
            //                        myRect = ClassPool.Get<TransableVertexOBBRect>();
            //                        float myRectSize = obstacle.radius * 2;
            //                        Vector3Logic moveDirEuler = Vector3Logic.DirectionToEulerXy(new Vector3Logic(moveDir.x, 0, moveDir.y));
            //                        int moveDirRotation = (int)Math.Round(moveDirEuler.y);
            //                        myRect.Init(entityPos2D, myRectSize, myRectSize, moveDirRotation);
            //                        //转换坐标系，以被推动实体位置为原点，移动方向为y轴，方便计算
            //                        myRect.TransformByNewAxis(entityPos2D, moveDir);
            //                    }
            //                    //创建其他实体的碰撞矩形
            //                    TransableVertexOBBRect otherRect = ClassPool.Get<TransableVertexOBBRect>();
            //                    Vector2Logic otherPos = new Vector2Logic(otherEntity.position.x, otherEntity.position.z);
            //                    otherRect.Init(otherPos, otherObstacle.rectW, otherObstacle.rectH, 0);//矩形障碍物不旋转
            //                    //转换坐标系，以被推动实体位置为原点，移动方向为y轴，方便计算
            //                    otherRect.TransformByNewAxis(entityPos2D, moveDir);

            //                    //预测碰撞，获得停止点
            //                    if (GeometryUtil.PredictMovingBoxCenterOnCollision(myRect, otherRect, maxDistance, out var endCenterPoint))
            //                    {
            //                        float sqrDistance = (endCenterPoint - entityPos2D).sqrMagnitude;
            //                        if (sqrDistance < minSqrDistance)
            //                        {
            //                            finalEndPoint = endCenterPoint;
            //                            minSqrDistance = sqrDistance;
            //                        }
            //                    }
            //                    //注意释放
            //                    ClassPool.Put(otherRect);
            //                }
            //            }
            //        }
            //        if (myRect != null)
            //        {
            //            //注意释放
            //            ClassPool.Put(myRect);
            //        }
            //    }
            //    ListPool<LevelEntity>.Put(entitiesAround);
            //}
            //#endregion 从移动范围内的实体中找到最近的碰撞点
            //if ((finalEndPoint - entityPos2D).sqrMagnitude < 1) //过于接近
            //{
            //    return;
            //}
            ////开始移动箱子
            //Vector3Logic stopPoint = new Vector3Logic(finalEndPoint.x, 0, finalEndPoint.y);
            //LgEntityLinearMove moveCom = entity.AddComponent<LgEntityLinearMove>();
            //moveCom.MoveTo(stopPoint, 400, EntityMoveDirType.KeepRotation);
        }

        /// <summary>
        /// 获取推动方向
        /// 逻辑是看实体前后左右四个方向离x轴正方向最近的那个，选它作为推动四方向里的x轴正方向（相当于在原世界四方向里加入实体的偏移）
        /// 然后根据英雄和实体的位置，计算英雄->实体方向上，离四个方向最近的那个方向，作为实际推动方向
        /// </summary>
        /// <param name="dirPosX">世界x轴正方向</param>
        /// <param name="entityDir">实体朝向</param>
        /// <param name="heroToEntity">英雄->实体的方向</param>
        /// <returns></returns>
        private Vector2Logic GetPushDir(Vector2Logic dirPosX, Vector3Logic entityDir, Vector2Logic heroToEntity)
        {
            Vector2Logic moveDir;
            //东
            if (entityDir.x != 0 && entityDir.z != 0)
            {
                dirPosX = GetEntityClosestToEastDir(dirPosX, new Vector2Logic(entityDir.x, entityDir.z));
            }
            //南
            Vector2Logic dirPosNegY = new Vector2Logic(dirPosX.y, -dirPosX.x);
            //西
            Vector2Logic dirPosNegX = new Vector2Logic(-dirPosX.x, -dirPosX.y);
            //北
            Vector2Logic dirPosY = new Vector2Logic(-dirPosX.y, dirPosX.x);

            //夹角最小的dir为moveDir
            //先算东
            float maxDotProduct = Vector2Logic.Dot(heroToEntity, dirPosX);
            moveDir = dirPosX;
            //南
            float dotProduct = Vector2Logic.Dot(heroToEntity, dirPosNegY);
            if (dotProduct > maxDotProduct)
            {
                maxDotProduct = dotProduct;
                moveDir = dirPosNegY;
            }
            //西
            dotProduct = Vector2Logic.Dot(heroToEntity, dirPosNegX);
            if (dotProduct > maxDotProduct)
            {
                maxDotProduct = dotProduct;
                moveDir = dirPosNegX;
            }
            //北
            dotProduct = Vector2Logic.Dot(heroToEntity, dirPosY);
            if (dotProduct > maxDotProduct)
            {
                moveDir = dirPosY;
            }
            return moveDir;
        }

        private Vector2Logic GetEntityClosestToEastDir(Vector2Logic east, Vector2Logic entityDir)
        {
            Vector2Logic forward = entityDir;
            Vector2Logic left = new Vector2Logic(forward.y, -forward.x);
            Vector2Logic back = new Vector2Logic(-forward.x, -forward.y);
            Vector2Logic right = new Vector2Logic(-forward.y, forward.x);
            Vector2Logic closestDir;
            //检查前方
            float maxDot = Vector2Logic.Dot(forward, east);
            closestDir = forward;
            // 检查左方
            float downDot = Vector2Logic.Dot(left, east);
            if (downDot > maxDot)
            {
                maxDot = downDot;
                closestDir = left;
            }
            // 检查左方
            float leftDot = Vector2Logic.Dot(back, east);
            if (leftDot > maxDot)
            {
                maxDot = leftDot;
                closestDir = back;
            }
            // 检查右方
            float rightDot = Vector2Logic.Dot(right, east);
            if (rightDot > maxDot)
            {
                closestDir = right;
            }
            return closestDir;
        }
    }
}
